/*
 * @(#)JideTabbedPane.java	Oct 7, 2002
 *
 * Copyright 2002 JIDE Software Inc. All rights reserved.
 */
package com.jidesoft.swing;

import com.jidesoft.plaf.JideTabbedPaneUI;
import com.jidesoft.plaf.LookAndFeelFactory;
import com.jidesoft.plaf.UIDefaultsLookup;
import com.jidesoft.plaf.basic.BasicJideTabbedPaneUI;
import com.jidesoft.popup.JidePopup;
import com.jidesoft.utils.JideFocusTracker;
import com.jidesoft.utils.PortingUtils;
import com.jidesoft.utils.SystemInfo;

import javax.swing.*;
import javax.swing.plaf.TabbedPaneUI;
import javax.swing.plaf.UIResource;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * <code>JideTabbedPane</code> 是对 <code>JTabbedPane</code> 的增强版. 不同于
 * <code>JTabbedPane</code>, 它
 * <ul>
 * <li>有一个选项可以隐藏标签页的区域，如果在标签页面板中只有一个组件 .
 * <li>有一个选项可以调整标签的宽度，以便所有标签可以适配在一行中.
 * <li>有一个选项可以显示一个关闭按钮，随着在标签区域向左滚动和向右滚动.
 * </ul>
 * 除以上需要设置额外选项的方法之外, <code>JideTabbedPane</code> 的用法和 <code>JTabbedPane</code>
 * 是一样的.
 */
public class JideTabbedPane extends JTabbedPane {

	private boolean _hideOneTab = false;

	private boolean _showTabButtons = false;

	private boolean _showCloseButton = false;
	private boolean _showCloseButtonOnTab = false;
	private boolean _showCloseButtonOnMouseOver = false;
	private boolean _useDefaultShowCloseButtonOnTab = false;
	private boolean _showTabArea = true;
	private boolean _showTabContent = true;

	private boolean _showIconsOnTab = true;
	private boolean _useDefaultShowIconsOnTab = true;

	private boolean _rightClickSelect;
	private boolean _dragOverDisabled;

	private boolean _scrollSelectedTabOnWheel = false;

	private int _tabAlignment = SwingConstants.LEADING;

	/**
	 * Bound property name for shrink tabs.
	 */
	public static final String SHRINK_TAB_PROPERTY = "shrinkTab";

	/**
	 * Bound property name for hide tab area if there is only one tab.
	 */
	public static final String HIDE_IF_ONE_TAB_PROPERTY = "hideIfOneTab";

	/**
	 * Bound property name for show tab button.
	 */
	public static final String SHOW_TAB_BUTTONS_PROPERTY = "showTabButtons";

	/**
	 * Bound property name for box style
	 */
	public static final String BOX_STYLE_PROPERTY = "boxStyle";

	/**
	 * Bound property name for show icons on tab
	 */
	public static final String SHOW_ICONS_PROPERTY = "showIconsOnTab";

	/**
	 * Bound property name for using default show icons on tab value from
	 * UIDefaults
	 */
	public static final String USE_DEFAULT_SHOW_ICONS_PROPERTY = "useDefaultShowIconsOnTab";

	/**
	 * Bound property name for if showing close button on tab
	 */
	public static final String SHOW_CLOSE_BUTTON_ON_TAB_PROPERTY = "showCloseButtonOnTab";

	/**
	 * Bound property name for if showing close button
	 */
	public static final String SHOW_CLOSE_BUTTON_PROPERTY = "showCloseButton";

	/**
	 * Bound property name for if the tab area is visible.
	 */
	public static final String SHOW_TAB_AREA_PROPERTY = "showTabArea";

	/**
	 * Bound property name for if the tab area is visible.
	 */
	public static final String SHOW_TAB_CONTENT_PROPERTY = "showTabContent";

	/**
	 * Bound property name for tab closable.
	 */
	public static final String TAB_CLOSABLE_PROPERTY = "tabClosable";

	/**
	 * Bound property name for using default show close button on tab value from
	 * UIDefaults
	 */
	public static final String USE_DEFAULT_SHOW_CLOSE_BUTTON_ON_TAB_PROPERTY = "useDefaultShowCloseButtonOnTab";

	/**
	 * Bound property name for if the active tab title is in bold
	 */
	public static final String BOLDACTIVETAB_PROPERTY = "boldActiveTab";

	/**
	 * Bound property name for gripper.
	 */
	public static final String GRIPPER_PROPERTY = "gripper";

	public static final String PROPERTY_TAB_SHAPE = "tabShape";
	public static final String PROPERTY_COLOR_THEME = "colorTheme";
	public static final String PROPERTY_TAB_RESIZE_MODE = "tabResizeMode";
	public static final String PROPERTY_TAB_LEADING_COMPONENT = "tabLeadingComponent";
	public static final String PROPERTY_TAB_TRAILING_COMPONENT = "tabTrailingComponent";
	public static final String PROPERTY_TAB_COLOR_PROVIDER = "tabColorProvider";
	public static final String PROPERTY_CONTENT_BORDER_INSETS = "contentBorderInsets";
	public static final String PROPERTY_TAB_AREA_INSETS = "tabAreaInsets";
	public static final String PROPERTY_TAB_INSETS = "tabInsets";
	public static final String PROPERTY_DRAG_OVER_DISABLED = "dragOverDisabled";
	public static final String SCROLL_TAB_ON_WHEEL_PROPERTY = "scrollTabOnWheel";
	public static final String PROPERTY_SELECTED_INDEX = "selectedIndex";
	public static final String PROPERTY_SHOW_CLOSE_BUTTON_ON_MOUSE_OVER = "showCloseButtonOnMouseOver";

	public static final int BUTTON_CLOSE = 0;
	public static final int BUTTON_EAST = 1;
	public static final int BUTTON_WEST = 2;
	public static final int BUTTON_NORTH = 3;
	public static final int BUTTON_SOUTH = 4;
	public static final int BUTTON_LIST = 5;

	/**
	 * @see #getUIClassID
	 * @see #readObject
	 */
	private static final String uiClassID = "JideTabbedPaneUI";

	/**
	 * If the gripper should be shown. Gripper is something on divider to
	 * indicate it can be dragged.
	 */
	private boolean _showGripper = false;

	/**
	 * A converter to shorten
	 */
	private StringConverter _stringConverter;

	private boolean _boldActiveTab = false;

	/**
	 * The Set for the tab closable. If there is an entry in the Set, it means
	 * the tab is NOT closable.
	 */
	private Set<Object> _closableSet = new HashSet<Object>();

	private Hashtable<Component, Object> _pageLastFocusTrackers = new Hashtable<Component, Object>();

	private Font _selectedTabFont;

	/**
	 * A tab resize mode. The default resize mode means it will use the resize
	 * mode of {@link #getDefaultTabResizeMode()} which is defined in UIDefault
	 * "JideTabbedPane.defaultResizeMode". You can change this in UIDefault. It
	 * will affect the resize mode of all <code>JideTabbedPane</code>s.
	 */
	public static final int RESIZE_MODE_DEFAULT = 0;

	/**
	 * A tab resize mode. The none resize mode means the tab will not resize
	 * when tabbed pane width changes.
	 */
	public static final int RESIZE_MODE_NONE = 1;

	/**
	 * A tab resize mode. The fit resize mode means the tabs will shrink if the
	 * tabbed pane width shrinks so there is no way to display the full contents
	 * of the tabs.
	 */
	public static final int RESIZE_MODE_FIT = 2;

	/**
	 * A tab resize mode. All tabs will be at a fixed width. The fixed width is
	 * defined as UIDefault "JideTabbedPane.fixedStyleRectSize" which is an
	 * integer.
	 */
	public static final int RESIZE_MODE_FIXED = 3;

	/**
	 * A tab resize mode. In this mode, the select tab will have full tab width.
	 * Non-selected tab will only display the icon. The actual width of
	 * non-selected tab is determined by UIDefault
	 * "JideTabbedPane.compressedStyleNoIconRectSize" which is an integer.
	 */
	public static final int RESIZE_MODE_COMPRESSED = 4;

	private int _tabResizeMode = RESIZE_MODE_DEFAULT;

	/**
	 * 颜色风格
	 */
	public static final int COLOR_THEME_DEFAULT = 0;
	public static final int COLOR_THEME_WIN2K = 1;
	public static final int COLOR_THEME_OFFICE2003 = 2;
	public static final int COLOR_THEME_VSNET = 3;
	public static final int COLOR_THEME_WINXP = 4;

	// 颜色主题
	private int _colorTheme = COLOR_THEME_DEFAULT;

	// 标签形状
	public static final int SHAPE_DEFAULT = 0;
	public static final int SHAPE_WINDOWS = 1;
	public static final int SHAPE_VSNET = 2;
	public static final int SHAPE_BOX = 3;
	public static final int SHAPE_OFFICE2003 = 4;
	public static final int SHAPE_FLAT = 5;
	public static final int SHAPE_ECLIPSE = 6;
	public static final int SHAPE_ECLIPSE3X = 7;
	public static final int SHAPE_EXCEL = 8;
	public static final int SHAPE_ROUNDED_VSNET = 9;
	public static final int SHAPE_ROUNDED_FLAT = 10;
	public static final int SHAPE_WINDOWS_SELECTED = 11;

	private int _tabShape = SHAPE_DEFAULT;

	private Component _tabLeadingComponent = null;
	private Component _tabTrailingComponent = null;
	private boolean _hideTrailingWhileNoButtons = false;

	// show close button on active tab only
	private boolean _showCloseButtonOnSelectedTab = false;

	private ListCellRenderer _tabListCellRenderer;

	private Insets _contentBorderInsets;
	private Insets _tabAreaInsets;
	private Insets _tabInsets;

	private static final Logger LOGGER_EVENT = Logger
			.getLogger(TabEditingEvent.class.getName());

	private boolean _closeTabOnMouseMiddleButton = false;
	private boolean _layoutTrailingComponentBeforeButtons = false;
	private JidePopup _tabListPopup;

	/**
	 * Creates an empty <code>TabbedPane</code> with a default tab placement of
	 * <code>JTabbedPane.TOP</code>.
	 *
	 * @see #addTab
	 */
	public JideTabbedPane() {
		this(JideTabbedPane.TOP, JideTabbedPane.SCROLL_TAB_LAYOUT);
	}

	/**
	 * Creates an empty <code>TabbedPane</code> with the specified tab placement
	 * of either: <code>JTabbedPane.TOP</code>, <code>JTabbedPane.BOTTOM</code>,
	 * <code>JTabbedPane.LEFT</code>, or <code>JTabbedPane.RIGHT</code>.
	 *
	 * @param tabPlacement
	 *            the placement for the tabs relative to the content
	 * @see #addTab
	 */
	public JideTabbedPane(int tabPlacement) {
		this(tabPlacement, JideTabbedPane.SCROLL_TAB_LAYOUT);
	}

	/**
	 * Creates an empty <code>JideTabbedPane</code> with the specified tab
	 * placement and tab layout policy. Tab placement may be either:
	 * <code>JTabbedPane.TOP</code> or <code>JTabbedPane.BOTTOM</code> Tab
	 * layout policy should always be <code>JTabbedPane.SCROLL_TAB_LAYOUT</code>
	 * . <code>JTabbedPane</code> also supports
	 * <code>JTabbedPane.WRAP_TAB_LAYOUT</code>. However the style of tabs in
	 * <code>JideTabbedPane</code> doesn't match with
	 * <code>JTabbedPane.WRAP_TAB_LAYOUT</code> very well, so we decided not to
	 * support it.
	 *
	 * @param tabPlacement
	 *            the placement for the tabs relative to the content
	 * @param tabLayoutPolicy
	 *            the policy for laying out tabs when all tabs will not fit on
	 *            one run
	 * @throws IllegalArgumentException
	 *             if tab placement or tab layout policy are not one of the
	 *             above supported values
	 * @see #addTab
	 */
	public JideTabbedPane(int tabPlacement, int tabLayoutPolicy) {
		super(tabPlacement, tabLayoutPolicy);
		// if(tabLayoutPolicy == WRAP_TAB_LAYOUT)
		// tabLayoutPolicy = SCROLL_TAB_LAYOUT;

		setModel(new IgnoreableSingleSelectionModel());
	}

	/**
	 * Returns the UI object which implements the L&F for this component.
	 *
	 * @return a <code>TabbedPaneUI</code> object
	 * @see #setUI
	 */
	@Override
	public TabbedPaneUI getUI() {
		return (TabbedPaneUI) ui;
	}

	/**
	 * Sets the UI object which implements the L&F for this component.
	 *
	 * @param ui
	 *            the new UI object
	 * @see UIDefaults#getUI
	 */
	@Override
	public void setUI(TabbedPaneUI ui) {
		super.setUI(ui);
	}

	/**
	 * Resets the UI property to a value from the current look and feel.
	 *
	 * @see JComponent#updateUI
	 */
	@Override
	public void updateUI() {
		if (UIDefaultsLookup.get(uiClassID) == null) {
			LookAndFeelFactory.installJideExtension();
		}
		setUI((TabbedPaneUI) UIManager.getUI(JideTabbedPane.this));
	}

	/**
	 * Returns the name of the UI class that implements the L&F for this
	 * component.
	 *
	 * @return the string "TabbedPaneUI"
	 * @see JComponent#getUIClassID
	 * @see UIDefaults#getUI
	 */
	@Override
	public String getUIClassID() {
		return uiClassID;
	}

	/**
	 * Checks if tab area will be hidden if there is only one tab. <br>
	 * If the showTabButtons option is true, isHideOneTab will always return
	 * false so that there is a place to place those tab buttons.
	 *
	 * @return true if tab areas will be hidden if there is only one tab; false
	 *         otherwise.
	 */
	public boolean isHideOneTab() {
		return !isShowTabButtons() && _hideOneTab;
	}

	/**
	 * Sets the value if tab area will be hidden if there is only one tab.
	 * PropertyChangeEvent of HIDE_IF_ONE_TAB_PROPERTY will be fired. <br>
	 * If the showTabButtons option is true, no matter what option you pass to
	 * setHideOneTab, isHideOneTab will always return false.
	 *
	 * @param hideOne
	 *            true to hide tab areas if there is only one tab; false
	 *            otherwise.
	 */
	public void setHideOneTab(boolean hideOne) {
		boolean oldValue = _hideOneTab;

		if (oldValue != hideOne) {
			_hideOneTab = hideOne;
			firePropertyChange(HIDE_IF_ONE_TAB_PROPERTY, oldValue, _hideOneTab);
		}
	}

	/**
	 * Checks if tab area is shown.
	 *
	 * @return true if tab area is visible; false otherwise.
	 */
	public boolean isTabShown() {
		return isShowTabArea() && !(isHideOneTab() && getTabCount() <= 1);
	}

	/**
	 * Checks if tab buttons are always visible. Tab buttons are scroll left
	 * button, scroll right button and close button which appear to the right of
	 * tabs in tab area. <br>
	 * If the showTabButtons is set to true, isHideOneTab will always return
	 * false so that there is a place to place those tab buttons.
	 *
	 * @return true if tab buttons are always visible; false otherwise.
	 */
	public boolean isShowTabButtons() {
		return _showTabButtons;
	}

	/**
	 * Sets the value if tab buttons are always visible. PropertyChangeEvent of
	 * SHOW_TAB_BUTTONS_PROPERTY will be fired.
	 *
	 * @param showButtons
	 *            true to always show tab buttons; false otherwise.
	 */
	public void setShowTabButtons(boolean showButtons) {
		boolean oldValue = _showTabButtons;

		if (oldValue != showButtons) {
			_showTabButtons = showButtons;
			firePropertyChange(SHOW_TAB_BUTTONS_PROPERTY, oldValue,
					_showTabButtons);
		}
	}

	private Action _closeAction;

	/**
	 * Sets default close action for close button.
	 *
	 * @param action
	 *            the close action.
	 */
	public void setCloseAction(Action action) {
		Action old = _closeAction;
		if (old != action) {
			_closeAction = action;
			firePropertyChange("closeTabAction", old, _closeAction);
		}
	}

	/**
	 * Gets close action.
	 *
	 * @return close action
	 */
	public Action getCloseAction() {
		return _closeAction;
	}

	public void setAutoFocusOnTabHideClose(boolean autoFocusonTabHideClose) {
		_autoFocusonTabHideClose = autoFocusonTabHideClose;
	}

	public boolean isAutoFocusOnTabHideClose() {
		return _autoFocusonTabHideClose;
	}

	boolean _autoFocusonTabHideClose = true;

	/**
	 * Resets close action to default. Default action is to remove currently
	 * selected tab.
	 */
	public void resetDefaultCloseAction() {
		setCloseAction(null);
	}

	private boolean _suppressStateChangedEvents = false;

	public void setSuppressStateChangedEvents(boolean suppress) {
		_suppressStateChangedEvents = suppress;
	}

	public boolean isSuppressStateChangedEvents() {
		return _suppressStateChangedEvents;
	}

	@Override
	protected void fireStateChanged() {
		if (_suppressStateChangedEvents)
			return;

		if (!isAutoFocusOnTabHideClose())
			clearVisComp();
		super.fireStateChanged();

	}

	// setSelectedIndex will be called during moving tab. So we use this flag to
	// suppress it.
	private boolean _suppressSetSelectedIndex = false;

	public boolean isSuppressSetSelectedIndex() {
		return _suppressSetSelectedIndex;
	}

	public void setSuppressSetSelectedIndex(boolean suppressSetSelectedIndex) {
		_suppressSetSelectedIndex = suppressSetSelectedIndex;
	}

	@Override
	public void setSelectedIndex(int index) {
		if (_suppressSetSelectedIndex)
			return;

		boolean old = isFocusCycleRoot();
		setFocusCycleRoot(true);
		try {
			int oldIndex = getSelectedIndex();
			if (oldIndex != index) {
				super.setSelectedIndex(index);
				firePropertyChange(PROPERTY_SELECTED_INDEX, oldIndex, index);
			}
		} finally {
			setFocusCycleRoot(old);
		}
	}

	/*
	 * This is called by the popup menu in the scrollrect area
	 */

	public void popupSelectedIndex(int index) {
		setSelectedIndex(index);
	}

	public void setComponentAt(int index, Component c) {
		Component oldComponent = getComponentAt(index);
		if (oldComponent != null) {
			// JTabbedPane allows a null c, but doesn't really support it.
			PageLastFocusTracker tracker = (PageLastFocusTracker) _pageLastFocusTrackers
					.get(oldComponent);
			_pageLastFocusTrackers.remove(oldComponent);
			if (tracker != null) {
				tracker.setHeighestComponent(null); // Clear its listeners
			}
		}

		boolean contains = false;
		if (_closableSet.contains(oldComponent)) {
			contains = true;
		}

		super.setComponentAt(index, c);

		if (contains) {
			_closableSet.add(c);
		}

		if (!isAutoFocusOnTabHideClose())
			clearVisComp();
	}

	private boolean _autoRequestFocus = true;

	/**
	 * Checks if the UI should automatically request focus on selected component
	 * when doing the layout. This method is only used internally when the tab
	 * is being moved.
	 *
	 * @return true or false. Default is true.
	 */
	public boolean isAutoRequestFocus() {
		return _autoRequestFocus;
	}

	public void setAutoRequestFocus(boolean autoRequestFocus) {
		_autoRequestFocus = autoRequestFocus;
	}

	/**
	 * Moves selected tab from current position to the position specified in
	 * tabIndex.
	 *
	 * @param tabIndex
	 *            new index
	 */
	public void moveSelectedTabTo(int tabIndex) {
		int selectedIndex = getSelectedIndex();
		if (selectedIndex == tabIndex) { // do nothing
			return;
		}
		if (tabIndex == -1 || selectedIndex == -1)
			return;

		if (isTabEditing())
			stopTabEditing();

		Component selectedComponent = getComponentAt(selectedIndex);

		boolean old = isAutoRequestFocus();

		boolean shouldChangeFocus = false;
		// we will not let UI to auto request focus so we will have to do it
		// here.
		// if the selected component has focus, we will request it after the tab
		// is moved.
		if (selectedComponent != null) {
			if (JideSwingUtilities.isAncestorOfFocusOwner(selectedComponent)
					&& isAutoFocusOnTabHideClose()) {
				shouldChangeFocus = true;
			}
		}

		try {
			_suppressStateChangedEvents = true;
			setAutoRequestFocus(false);

			if (selectedIndex - tabIndex == 1 || tabIndex - selectedIndex == 1) {
				Component c = getComponentAt(tabIndex);
				String title = getTitleAt(tabIndex);
				String tooltip = getToolTipTextAt(tabIndex);
				Icon icon = getIconAt(tabIndex);
				_suppressSetSelectedIndex = true;
				boolean closable = true;
				if (_closableSet != null) {
					closable = isTabClosableAt(tabIndex);
				}
				try {
					if (tabIndex > selectedIndex)
						insertTab(title, icon, c, tooltip, selectedIndex);
					else {
						insertTab(title, icon, c, tooltip, selectedIndex + 1);
					}
					if (!closable) {
						_closableSet.add(c);
					}
				} finally {
					_suppressSetSelectedIndex = false;
				}
			} else {
				Component c = getComponentAt(selectedIndex);
				String title = getTitleAt(selectedIndex);
				String tooltip = getToolTipTextAt(selectedIndex);
				Icon icon = getIconAt(selectedIndex);
				_suppressSetSelectedIndex = true;
				boolean closable = true;
				if (_closableSet != null) {
					closable = isTabClosableAt(tabIndex);
				}
				try {
					if (tabIndex > selectedIndex)
						insertTab(title, icon, c, tooltip, tabIndex + 1);
					else {
						insertTab(title, icon, c, tooltip, tabIndex);
					}
					if (!closable) {
						_closableSet.add(c);
					}
				} finally {
					_suppressSetSelectedIndex = false;
				}
			}

			if (!SystemInfo.isJdk15Above()) {
				// a workaround for Swing bug
				if (tabIndex == getTabCount() - 2) {
					setSelectedIndex(getTabCount() - 1);
				}
			}

			setAutoRequestFocus(old);
			setSelectedIndex(tabIndex);
		} finally {
			_suppressStateChangedEvents = false;

			if (shouldChangeFocus) {
				if (!requestFocusForVisibleComponent()) {
					// System.out.println("---tabpane.requestfocus41");
					requestFocusInWindow();
				}
			}
		}
	}

	// mtf - review if this is still needed

	public boolean requestFocusForVisibleComponent() {
		return false;
		/*
		 * if (true) return false; //
		 * System.out.println("---JideTabbedPane.requestFocusForVisibleComponent()"
		 * ); Component visibleComponent = getSelectedComponent(); Component
		 * lastFocused = getLastFocusedComponent(visibleComponent); if
		 * (lastFocused != null && lastFocused.requestFocusInWindow()) { return
		 * true; } else { // Focus the next component in the focus cycle after
		 * the tab. Container nearestRoot = (isFocusCycleRoot()) ? this :
		 * getFocusCycleRootAncestor(); if (nearestRoot == null) { return false;
		 * } Component comp =
		 * nearestRoot.getFocusTraversalPolicy().getComponentAfter(nearestRoot,
		 * this); return comp != null && comp.requestFocusInWindow() ||
		 * JideSwingUtilities.compositeRequestFocus(visibleComponent); }
		 */
	}

	/**
	 * Get the flag that if the trailing component should be hidden while no
	 * buttons are visible.
	 * <p/>
	 * Be default, the flag is false. If you want to connect visibility of those
	 * two components, please set it to true.
	 *
	 * @return true if the trailing component would be hidden while no buttons
	 *         are visible. Otherwise false.
	 * @see #isShowTabArea()
	 * @see #isShowTabButtons()
	 * @see #setHideTrailingWhileNoButtons(boolean)
	 */
	public boolean isHideTrailingWhileNoButtons() {
		return _hideTrailingWhileNoButtons;
	}

	/**
	 * Set the flag that if the trailing component should be hidden while no
	 * buttons are visible.
	 *
	 * @param hideTrailingWhileNoButtons
	 *            the flag
	 * @see #isHideTrailingWhileNoButtons()
	 */
	public void setHideTrailingWhileNoButtons(boolean hideTrailingWhileNoButtons) {
		_hideTrailingWhileNoButtons = hideTrailingWhileNoButtons;
	}

	/**
	 * Gets the flag indicating if the trailing component should be layout
	 * before the default buttons.
	 *
	 * @return true if the trailing component should be layout to the left/up.
	 *         Otherwise false.
	 * @see #setLayoutTrailingComponentBeforeButtons(boolean)
	 */
	public boolean isLayoutTrailingComponentBeforeButtons() {
		return _layoutTrailingComponentBeforeButtons;
	}

	/**
	 * Sets the flag indicating if the trailing component should be layout
	 * before the default buttons.
	 * <p/>
	 * The default value is false. If you want your trailing component preceding
	 * to the default buttons, please set this flag to true.
	 *
	 * @param layoutTrailingComponentBeforeButtons
	 *            the flag
	 */
	public void setLayoutTrailingComponentBeforeButtons(
			boolean layoutTrailingComponentBeforeButtons) {
		_layoutTrailingComponentBeforeButtons = layoutTrailingComponentBeforeButtons;
	}

	/*
	 * Used to allow the tabswitching to be delayed until after drag/reorder
	 * opperations are done.
	 */

	protected class IgnoreableSingleSelectionModel extends
			DefaultSingleSelectionModel {
		private static final long serialVersionUID = -4321082126384337792L;

		@Override
		protected void fireStateChanged() {
			if (!_suppressStateChangedEvents) {
				super.fireStateChanged();
			}
		}
	}

	@SuppressWarnings({ "UnusedDeclaration" })
	public void processMouseSelection(int tabIndex, MouseEvent e) {

	}

	/**
	 * Gets tab height.
	 *
	 * @return height of tab
	 */
	public int getTabHeight() {
		if (getTabPlacement() == TOP || getTabPlacement() == BOTTOM) {
			return ((JideTabbedPaneUI) getUI()).getTabPanel().getHeight();
		} else {
			return ((JideTabbedPaneUI) getUI()).getTabPanel().getWidth();
		}
	}

	/**
	 * Returns true if you want right click on unselected tab will select that
	 * tab.
	 *
	 * @return true if right click on unselected tab will select that tab
	 */
	public boolean isRightClickSelect() {
		return _rightClickSelect;
	}

	/**
	 * Sets if you want right click on unselected tab will select that tab.
	 *
	 * @param rightClickSelect
	 *            true if right click on unselected tab will select that tab
	 */
	public void setRightClickSelect(boolean rightClickSelect) {
		_rightClickSelect = rightClickSelect;
	}

	public int getTabAtLocation(int x, int y) {
		int tabCount = getTabCount();
		int i = getUI().tabForCoordinate(this, x, y);
		return i < 0 || i >= tabCount ? -1 : i;
	}

	/**
	 * If the grip is visible.
	 *
	 * @return true if grip is visible
	 */
	public boolean isShowGripper() {
		return _showGripper;
	}

	/**
	 * Sets the visibility of grip.
	 *
	 * @param showGripper
	 *            true to show grip
	 */
	public void setShowGripper(boolean showGripper) {
		boolean oldShowGripper = _showGripper;
		if (oldShowGripper != showGripper) {
			_showGripper = showGripper;
			firePropertyChange(GRIPPER_PROPERTY, oldShowGripper, _showGripper);
		}
	}

	/**
	 * Checks if the icon will be shown on tab.
	 *
	 * @return true if the icon will be shown on tab.
	 */
	public boolean isShowIconsOnTab() {
		return _showIconsOnTab;
	}

	/**
	 * Sets to true if the icon will be shown on tab. The value set to this
	 * method will be used only when isUseDefaultShowIconsOnTab() returns false.
	 *
	 * @param showIconsOnTab
	 *            true or false.
	 */
	public void setShowIconsOnTab(boolean showIconsOnTab) {
		boolean oldShowIconsOnTab = _showIconsOnTab;
		if (oldShowIconsOnTab != showIconsOnTab) {
			_showIconsOnTab = showIconsOnTab;
			firePropertyChange(SHOW_ICONS_PROPERTY, oldShowIconsOnTab,
					_showIconsOnTab);
		}
	}

	/**
	 * If the return is true, the value set to setShowIconsOnTab() will be
	 * ignored.
	 *
	 * @return if use default value from UIDefaults in L&F.
	 */
	public boolean isUseDefaultShowIconsOnTab() {
		return _useDefaultShowIconsOnTab;
	}

	/**
	 * Set if use the default value from UIDefaults.
	 *
	 * @param useDefaultShowIconsOnTab
	 *            true or false.
	 */
	public void setUseDefaultShowIconsOnTab(boolean useDefaultShowIconsOnTab) {
		boolean oldUseDefaultShowIconsOnTab = _useDefaultShowIconsOnTab;
		if (oldUseDefaultShowIconsOnTab != useDefaultShowIconsOnTab) {
			_useDefaultShowIconsOnTab = useDefaultShowIconsOnTab;
			firePropertyChange(USE_DEFAULT_SHOW_ICONS_PROPERTY,
					oldUseDefaultShowIconsOnTab, _useDefaultShowIconsOnTab);
		}
	}

	/**
	 * Checks if the close button will be shown on tab.
	 *
	 * @return true if close button will be shown on tab.
	 */
	public boolean isShowCloseButtonOnTab() {
		return _showCloseButtonOnTab;
	}

	/**
	 * Sets to true if the close button will be shown on tab. If you ever call
	 * this method, we will automatically call
	 * setUseDefaultShowCloseButtonOnTab(false). It will also automatically call
	 * setShowCloseButton(true) if the showCloseButtonOnTab parameter is true.
	 *
	 * @param showCloseButtonOnTab
	 *            true or false.
	 */
	public void setShowCloseButtonOnTab(boolean showCloseButtonOnTab) {
		boolean oldShowCloseButtonOnTab = _showCloseButtonOnTab;
		if (oldShowCloseButtonOnTab != showCloseButtonOnTab) {
			_showCloseButtonOnTab = showCloseButtonOnTab;
			firePropertyChange(SHOW_CLOSE_BUTTON_ON_TAB_PROPERTY,
					oldShowCloseButtonOnTab, _showCloseButtonOnTab);
			if (_showCloseButtonOnTab) {
				setShowCloseButton(true);
			}
		}
		setUseDefaultShowCloseButtonOnTab(false);
	}

	/**
	 * If the return is true, the value set to setShowCloseButtonOnTab() will be
	 * ignored.
	 *
	 * @return if use default value from UIDefaults in L&F.
	 */
	public boolean isUseDefaultShowCloseButtonOnTab() {
		return _useDefaultShowCloseButtonOnTab;
	}

	/**
	 * Set if use the default value from UIDefaults.
	 *
	 * @param useDefaultShowCloseButtonOnTab
	 *            true or false.
	 */
	public void setUseDefaultShowCloseButtonOnTab(
			boolean useDefaultShowCloseButtonOnTab) {
		boolean oldUseDefaultShowCloseButtonOnTab = _useDefaultShowCloseButtonOnTab;
		if (oldUseDefaultShowCloseButtonOnTab != useDefaultShowCloseButtonOnTab) {
			_useDefaultShowCloseButtonOnTab = useDefaultShowCloseButtonOnTab;
			firePropertyChange(USE_DEFAULT_SHOW_CLOSE_BUTTON_ON_TAB_PROPERTY,
					oldUseDefaultShowCloseButtonOnTab,
					_useDefaultShowCloseButtonOnTab);
		}
	}

	// below is the code to allow editing the tab title directly
	transient protected boolean _tabEditingAllowed = false;

	/**
	 * Sets the value if the tab editing is allowed. Tab editing allows user to
	 * edit the tab title directly by double clicking on the tab.
	 *
	 * @param allowed
	 *            true or false.
	 */
	public void setTabEditingAllowed(boolean allowed) {
		_tabEditingAllowed = allowed;
	}

	/**
	 * Checks if the tab editing is allowed.
	 *
	 * @return true if tab editing is allowed. Otherwise false.
	 */
	public boolean isTabEditingAllowed() {
		return _tabEditingAllowed && getTabLayoutPolicy() == SCROLL_TAB_LAYOUT;
	}

	transient protected TabEditingValidator _tabEditValidator;

	public void setTabEditingValidator(TabEditingValidator tabEditValidator) {
		_tabEditValidator = tabEditValidator;
	}

	public TabEditingValidator getTabEditingValidator() {
		return _tabEditValidator;
	}

	/**
	 * If close button is visible.
	 *
	 * @return true if the close button is visible.
	 */
	public boolean isShowCloseButton() {
		return _showCloseButton;
	}

	/**
	 * Sets if the close button is visible. Close button can be either side by
	 * side with scroll buttons, or on each tab. If you call
	 * setShowCloseButton(false), it will hide close buttons for both cases.
	 *
	 * @param showCloseButton
	 *            true or false.
	 */
	public void setShowCloseButton(boolean showCloseButton) {
		boolean oldShowCloseButton = _showCloseButton;
		if (oldShowCloseButton != showCloseButton) {
			_showCloseButton = showCloseButton;
			firePropertyChange(SHOW_CLOSE_BUTTON_PROPERTY, oldShowCloseButton,
					_showCloseButton);
		}
	}

	/**
	 * If the tab area is visible.
	 *
	 * @return true if the tab area is visible.
	 */
	public boolean isShowTabArea() {
		return _showTabArea;
	}

	/**
	 * Sets if the tab area is visible. If not visible, you can programatically
	 * call setSelectedIndex to change ta. User will not be able to do it by
	 * clicking on tabs since they are not visible.
	 *
	 * @param showTabArea
	 *            true or false.
	 */
	public void setShowTabArea(boolean showTabArea) {
		boolean oldShowTabArea = _showTabArea;
		if (oldShowTabArea != showTabArea) {
			_showTabArea = showTabArea;
			firePropertyChange(SHOW_TAB_AREA_PROPERTY, oldShowTabArea,
					_showTabArea);
		}
	}

	/**
	 * If the tab content is visible.
	 *
	 * @return true if the tab content is visible.
	 */
	public boolean isShowTabContent() {
		return _showTabContent;
	}

	/**
	 * Sets if the tab content is visible.
	 *
	 * @param showTabContent
	 *            true or false.
	 */
	public void setShowTabContent(boolean showTabContent) {
		boolean oldShowTabContent = _showTabContent;
		if (oldShowTabContent != showTabContent) {
			_showTabContent = showTabContent;
			firePropertyChange(SHOW_TAB_CONTENT_PROPERTY, oldShowTabContent,
					_showTabContent);
		}
	}

	/**
	 * Gets the string converter that converts the tab title to the display
	 * title.
	 *
	 * @return the converter that converts the tab title to the display title.
	 */
	public StringConverter getStringConverter() {
		return _stringConverter;
	}

	/**
	 * Sets the string converter.
	 *
	 * @param stringConverter
	 *            the StringConverter.
	 * @see #getStringConverter()
	 */
	public void setStringConverter(StringConverter stringConverter) {
		_stringConverter = stringConverter;
	}

	/**
	 * Gets the display title. Display title is result of using string converter
	 * that converts from the title to a display title. There is no setter for
	 * display title. You control the value by using a different string
	 * converter.
	 *
	 * @param index
	 *            the index to display
	 * @return the display title.
	 */
	public String getDisplayTitleAt(int index) {
		if (_stringConverter != null) {
			return _stringConverter.convert(super.getTitleAt(index));
		} else {
			return super.getTitleAt(index);
		}
	}

	/**
	 * If the active tab is in bold.
	 *
	 * @return if the active tab is in bold.
	 */
	public boolean isBoldActiveTab() {
		return _boldActiveTab;
	}

	/**
	 * Sets if the active tab is in bold.
	 *
	 * @param boldActiveTab
	 *            the flag
	 */
	public void setBoldActiveTab(boolean boldActiveTab) {
		boolean old = _boldActiveTab;
		if (old != boldActiveTab) {
			_boldActiveTab = boldActiveTab;
			firePropertyChange(BOLDACTIVETAB_PROPERTY, old, _boldActiveTab);
		}
	}

	@Override
	public void removeTabAt(int index) {
		int tabCount = getTabCount();
		int selected = getSelectedIndex();
		boolean enforce = false;
		if (selected == index && selected < tabCount - 1) {
			// since JDK5 fixed this, we only need to enforce the event when it
			// is not JDK5 and above.
			// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6368047
			enforce = !SystemInfo.isJdk15Above();
		}

		Component c = getComponentAt(index);
		boolean contains = false;
		if (_closableSet.contains(c)) {
			contains = true;
		}

		if (!isAutoFocusOnTabHideClose())
			clearVisComp();

		if (contains) {
			_closableSet.remove(c);
		}
		if (c != null) {
			// JTabbedPane allows a null c, but doesn't really support it.
			PageLastFocusTracker tracker = (PageLastFocusTracker) _pageLastFocusTrackers
					.get(c);
			_pageLastFocusTrackers.remove(c);
			if (tracker != null) {
				tracker.setHeighestComponent(null); // Clear its listeners
			}
		}

		if (getUI() instanceof BasicJideTabbedPaneUI) {
			((BasicJideTabbedPaneUI) getUI()).ensureActiveTabIsVisible(true);
			if (isAutoFocusOnTabHideClose() && hasFocusComponent()) {
				((BasicJideTabbedPaneUI) getUI())
						.requestFocusForVisibleComponent();
			}
		}

		super.removeTabAt(index);

		// We need to fire events
		if (enforce) {
			try {
				fireStateChanged();
			} catch (Throwable th) {
				th.printStackTrace();
			}
		}
	}

	/**
	 * Checks if the tab at tabIndex should show the close button. This is only
	 * a valid if showCloseButtonOnTab attribute is true.
	 * <p/>
	 * By default, this method always return true. Subclass can override this
	 * method to return a different value.
	 *
	 * @param tabIndex
	 *            the tab index
	 * @return the flag.
	 * @throws IndexOutOfBoundsException
	 *             if index is out of range (index < 0 || index >= tab count)
	 */
	public boolean isTabClosableAt(int tabIndex) {
		return !_closableSet.contains(getComponentAt(tabIndex));
	}

	/**
	 * Checks if the tab at tabIndex should show the close button. This is only
	 * a valid if showCloseButtonOnTab attribute is true.
	 * <p/>
	 * By default, this method always return true. Subclass can override this
	 * method to return a different value.
	 * <p/>
	 * Please note, this attribute has effect only when
	 * {@link #isShowCloseButtonOnTab()} return true.
	 *
	 * @param tabIndex
	 *            the tab index
	 * @param closable
	 *            the flag indicating if the tab is closable
	 * @throws IndexOutOfBoundsException
	 *             if index is out of range (index < 0 || index >= tab count)
	 */
	public void setTabClosableAt(int tabIndex, boolean closable) {
		if (closable) {
			_closableSet.remove(getComponentAt(tabIndex));
		} else {
			_closableSet.add(getComponentAt(tabIndex));
		}
		firePropertyChange(TAB_CLOSABLE_PROPERTY, !closable, closable);
	}

	protected Hashtable getPageLastFocusTrackers() {
		return _pageLastFocusTrackers;
	}

	/**
	 * Gets the last focused component of a particular page.
	 *
	 * @param pageComponent
	 *            the page component
	 * @return the last focused component of a particular page.
	 */

	public Component getLastFocusedComponent(Component pageComponent) {
		if (pageComponent == null) {
			return null;
		}
		// System.out.println("---JideTabbedPane.getLastFocusedComponent()" +
		// pageComponent);

		PageLastFocusTracker tracker = (PageLastFocusTracker) (getPageLastFocusTrackers()
				.get(pageComponent));

		// System.out.println("---JideTabbedPane.getLastFocusedComponent()" +
		// componentReturn);
		// if (false) {
		// Component compTest = new JPanel() {
		// public void requestFocus() {
		// // System.out.println("---.requestFocus()22");
		// componentReturn.requestFocus();
		// }
		//
		// public boolean isRequestFocusEnabled() {
		// // System.out.println("---.isRequestFocusEnabled()");
		// return true;
		// }
		//
		// public Container getParent() {
		// // System.out.println("---.getParent()");
		// return (Container) componentReturn;
		// }
		// };
		// if (componentReturn != null)
		// ((Container) componentReturn).add(compTest);
		// return compTest;
		// }
		return ((tracker != null) ? tracker.getLastFocusedComponent() : null);
	}

	protected void clearVisComp() {
		// this is done so that the super removetab and fireselection do not
		// attempt to manage focus
		// A very dirty hack to access a private variable is jtabpane. Note -
		// this only works on 1.6
		try {
			java.lang.reflect.Field field = JTabbedPane.class
					.getDeclaredField("visComp");
			// set accessible true
			field.setAccessible(true);
			field.set(this, null);
			// superVisComp = (Component) field.get(this);
		} catch (Exception e) {
			// null
		}
	}

	/**
	 * Overridden to add a <code>PageLastFocusTracker</code> to each page, used
	 * to update the page's last focused component.
	 */
	@Override
	public void insertTab(String title, Icon icon, Component component,
			String tip, int index) {
		// set the component to visible false initially because the layout
		// manager will set it to visible when
		// appropriate. This also limits the flicker from mixing
		// lightweight/heavyweight components.
		if (component != null) {
			if (component == getTabLeadingComponent()
					|| component == getTabTrailingComponent()) {
				return;
			}
		}
		if (component != null && !component.isVisible())
			component.setVisible(false);

		super.insertTab(title, icon, component, tip, index);

		if (component != null) {
			// JTabbedPane allows a null component, but doesn't really support
			// it.
			_pageLastFocusTrackers.put(component, new PageLastFocusTracker(
					component));
		}

		// fireStateChanged();
	}

	protected class PageLastFocusTracker extends JideFocusTracker {
		// keep track of last focused component
		private Component _lastFocusedComponent;

		private FocusListener _lastFocusedListener;

		protected PageLastFocusTracker(Component pageComp) {
			this.setHeighestComponent(pageComp);
		}

		protected Component getLastFocusedComponent() {
			return _lastFocusedComponent;
		}

		@Override
		public void setHeighestComponent(Component compHeighest) {
			if (compHeighest == null) {
				if (_lastFocusedListener != null) {
					this.removeFocusListener(_lastFocusedListener);
					_lastFocusedListener = null;
				}
			} else {
				if (_lastFocusedListener == null) {
					_lastFocusedListener = new FocusAdapter() {
						@Override
						public void focusGained(FocusEvent e) {
							_lastFocusedComponent = e.getComponent();
						}
					};
					this.addFocusListener(_lastFocusedListener);
				}
			}
			super.setHeighestComponent(compHeighest);
		}
	}

	/**
	 * Gets the font for selected tab.
	 *
	 * @return the font for selected tab.
	 */
	public Font getSelectedTabFont() {
		return _selectedTabFont;
	}

	/**
	 * Sets the font for selected tab.
	 *
	 * @param selectedTabFont
	 *            new font for selected tab.
	 */
	public void setSelectedTabFont(Font selectedTabFont) {
		_selectedTabFont = selectedTabFont;
	}

	public int getColorTheme() {
		if (_colorTheme == COLOR_THEME_DEFAULT) {
			return getDefaultColorTheme();
		} else {
			return _colorTheme;
		}
	}

	public int getDefaultColorTheme() {
		return UIDefaultsLookup.getInt("JideTabbedPane.defaultTabColorTheme");
	}

	public void setColorTheme(int colorTheme) {
		int old = _colorTheme;
		if (old != colorTheme) {
			_colorTheme = colorTheme;
			firePropertyChange(PROPERTY_COLOR_THEME, old, colorTheme);
		}
	}

	public int getTabResizeMode() {
		if (_tabResizeMode == RESIZE_MODE_DEFAULT) {
			return getDefaultTabResizeMode();
		} else {
			return _tabResizeMode;
		}
	}

	/**
	 * Sets the tab resize mode. There are five resize modes. -
	 * {@link #RESIZE_MODE_DEFAULT}, {@link #RESIZE_MODE_NONE},
	 * {@link #RESIZE_MODE_FIT}, {@link #RESIZE_MODE_FIXED} and
	 * {@link #RESIZE_MODE_COMPRESSED}.
	 *
	 * @param resizeMode
	 *            the new resize mode.
	 */
	public void setTabResizeMode(int resizeMode) {
		int old = _tabResizeMode;
		if (old != resizeMode) {
			_tabResizeMode = resizeMode;
			firePropertyChange(PROPERTY_TAB_RESIZE_MODE, old, resizeMode);
		}
	}

	public int getDefaultTabResizeMode() {
		return UIDefaultsLookup.getInt("JideTabbedPane.defaultResizeMode");
	}

	public int getTabShape() {
		if (_tabShape == SHAPE_DEFAULT) {
			return getDefaultTabStyle();
		} else {
			return _tabShape;
		}
	}

	public int getDefaultTabStyle() {
		return UIDefaultsLookup.getInt("JideTabbedPane.defaultTabShape");
	}

	/**
	 * 设置标签的形状
	 * @param tabShape 标签形状
	 */
	public void setTabShape(int tabShape) {
		int old = _tabShape;
		if (old != tabShape) {
			_tabShape = tabShape;
			firePropertyChange(PROPERTY_TAB_SHAPE, old, _tabShape);
		}
	}

	/**
	 * Sets the tab leading component. The tab leading component will appear
	 * before the tabs in the tab area. Please note, you must implement
	 * UIResource for the component you want to use as tab leading component.
	 *
	 * @param component
	 *            the tab leading component
	 * @throws IllegalArgumentException
	 *             if the component doesn't implement UIResource.
	 */
	public void setTabLeadingComponent(Component component) {
		// if (component != null && !(component instanceof UIResource)) {
		// throw new
		// IllegalArgumentException("TabLeadingComponent must implement javax.swing.plaf.UIResource interface.");
		// }
		Component old = _tabLeadingComponent;
		_tabLeadingComponent = component;
		firePropertyChange(PROPERTY_TAB_LEADING_COMPONENT, old, component);
	}

	public Component getTabLeadingComponent() {
		return _tabLeadingComponent;
	}

	/**
	 * Sets the tab trailing component. The tab trailing component will appear
	 * after the tabs in the tab area. Please note, you must implement
	 * UIResource for the component you want to use as tab trailing component.
	 *
	 * @param component
	 *            the tab trailing component
	 * @throws IllegalArgumentException
	 *             if the component doesn't implement UIResource.
	 */
	public void setTabTrailingComponent(Component component) {
		// if (component != null && !(component instanceof UIResource)) {
		// throw new
		// IllegalArgumentException("TabLeadingComponent must implement javax.swing.plaf.UIResource interface.");
		// }
		Component old = _tabTrailingComponent;
		_tabTrailingComponent = component;
		firePropertyChange(PROPERTY_TAB_TRAILING_COMPONENT, old, component);
	}

	@Override
	public Component add(Component component) {
		if (!(component instanceof UIResource)
				&& component != getTabTrailingComponent()
				&& component != getTabLeadingComponent()) {
			addTab(component.getName(), component);
		} else {
			addImpl(component, null, -1);
		}
		return component;
	}

	public Component getTabTrailingComponent() {
		return _tabTrailingComponent;
	}

	public boolean isShowCloseButtonOnSelectedTab() {
		return _showCloseButtonOnSelectedTab;
	}

	/**
	 * Shows the close button on the selected tab only. You also need to
	 * setShowCloseButtonOnTab(true) and setShowCloseButton(true) if you want to
	 * setShowCloseButtonOnSelectedTab(true).
	 *
	 * @param i
	 *            the flag indicating if close button should be shown in the
	 *            selected tab
	 */
	public void setShowCloseButtonOnSelectedTab(boolean i) {
		_showCloseButtonOnSelectedTab = i;
	}

	/**
	 * Gets the flag indicating if the close button should only be displayed
	 * when the mouse is over the tab.
	 *
	 * @return true if the close button should only be displayed when the mouse
	 *         is over the tab. Otherwise false.
	 * @see #setShowCloseButtonOnMouseOver(boolean)
	 * @since 3.3.3
	 */
	public boolean isShowCloseButtonOnMouseOver() {
		return _showCloseButtonOnMouseOver;
	}

	/**
	 * Sets the flag indicating if the close button should only be displayed
	 * when the mouse is over the tab.
	 * <p/>
	 * The default value of the flag is false to keep default behavior not
	 * changed.
	 *
	 * @param showCloseButtonOnMouseOverOnly
	 *            the flag
	 * @since 3.3.3
	 */
	public void setShowCloseButtonOnMouseOver(
			boolean showCloseButtonOnMouseOverOnly) {
		if (_showCloseButtonOnMouseOver != showCloseButtonOnMouseOverOnly) {
			boolean old = _showCloseButtonOnMouseOver;
			_showCloseButtonOnMouseOver = showCloseButtonOnMouseOverOnly;
			firePropertyChange(PROPERTY_SHOW_CLOSE_BUTTON_ON_MOUSE_OVER, old,
					_showCloseButtonOnMouseOver);
		}
	}

	private ColorProvider _tabColorProvider;

	/**
	 * An interface to provide colors for tab background and foreground.
	 */
	public static interface ColorProvider {
		/**
		 * Gets the tab background for the tab at the specified index.
		 *
		 * @param tabIndex
		 *            the index of the tab
		 * @return the tab background for the tab at the specified index.
		 */
		Color getBackgroundAt(int tabIndex);

		/**
		 * Gets the tab foreground for the tab at the specified index.
		 *
		 * @param tabIndex
		 *            the index of the tab
		 * @return the tab foreground for the tab at the specified index.
		 */
		Color getForegroundAt(int tabIndex);

		/**
		 * Gets the gradient ratio. We will use this ratio to provide another
		 * color in order to paint gradient.
		 *
		 * @param tabIndex
		 *            the index of the tab
		 * @return the gradient ratio. The value should be between 0 and 1. 0
		 *         will produce the darkest and color and 1 will produce the
		 *         lightest color. 0.5 will provide the same color.
		 */
		float getGradientRatio(int tabIndex);
	}

	/**
	 * A ColorProvider that can supports gradient tab background. The
	 * ColorProvider can also do gradient but the other color has to be be a
	 * lighter or darker version of the color of getBackgroundAt.
	 * GradientColorProvider allows you to specify an independent color as the
	 * start color.
	 */
	public static interface GradientColorProvider extends ColorProvider {
		/**
		 * Gets the tab background at the top (or other direction depending on
		 * the tab placement) of the tab. The JideTabbedPaneUI will paint a
		 * gradient using this color and the color of getBackgroundAt.
		 *
		 * @param tabIndex
		 *            the index of the tab
		 * @return the top background color.
		 */
		Color getTopBackgroundAt(int tabIndex);
	}

	private static Color[] ONENOTE_COLORS = { new Color(138, 168, 228), // blue
			new Color(238, 149, 151), // pink
			new Color(180, 158, 222), // purple
			new Color(145, 186, 174), // cyan
			new Color(246, 176, 120), // gold
			new Color(255, 216, 105), // yellow
			new Color(183, 201, 151) // green
	};

	public static ColorProvider ONENOTE_COLOR_PROVIDER = new OneNoteColorProvider();

	private static class OneNoteColorProvider implements ColorProvider {
		public Color getBackgroundAt(int index) {
			return ONENOTE_COLORS[index % ONENOTE_COLORS.length];
		}

		public Color getForegroundAt(int index) {
			return Color.BLACK;
		}

		public float getGradientRatio(int tabIndex) {
			return 0.86f;
		}

	}

	/**
	 * Gets the tab color provider.
	 *
	 * @return tab color provider.
	 */
	public ColorProvider getTabColorProvider() {
		return _tabColorProvider;
	}

	/**
	 * Sets the tab color provider.It allows you to set the background color of
	 * each tab. The reason to use this way instead of
	 * {@link #setBackgroundAt(int, java.awt.Color)} method is because this way
	 * queries the color. So it can support unlimited number of tabs. When you
	 * don't know exactly how many tabs it will be, this way can still handle it
	 * very well. There is {@link #ONENOTE_COLOR_PROVIDER} which provides the
	 * tab color as you see in Microsoft OneNote 2003. You can also define your
	 * own ColorProvider to fit your application color theme.
	 *
	 * @param tabColorProvider
	 *            the tab color provider
	 */
	public void setTabColorProvider(ColorProvider tabColorProvider) {
		ColorProvider old = _tabColorProvider;
		if (old != tabColorProvider) {
			_tabColorProvider = tabColorProvider;
			firePropertyChange(PROPERTY_TAB_COLOR_PROVIDER, old,
					tabColorProvider);
		}
	}

	/**
	 * Starts tab editing. This works only when
	 * {@link #setTabEditingAllowed(boolean)} is set to true.
	 *
	 * @param tabIndex
	 *            the index of the tab
	 */
	public void editTabAt(int tabIndex) {
		boolean started = ((JideTabbedPaneUI) getUI()).editTabAt(tabIndex);
		if (started) {
			fireTabEditing(TabEditingEvent.TAB_EDITING_STARTED, tabIndex,
					getTitleAt(tabIndex), null);
		}
	}

	/**
	 * Checks if tab is in editing mode.
	 *
	 * @return true if editing.
	 */
	public boolean isTabEditing() {
		return ((JideTabbedPaneUI) getUI()).isTabEditing();
	}

	public void stopTabEditing() {
		int tabIndex = getEditingTabIndex();
		if (tabIndex != -1 && tabIndex < getTabCount()) {
			String oldTitle = getTitleAt(tabIndex);
			((JideTabbedPaneUI) getUI()).stopTabEditing();
			String newTitle = getTitleAt(tabIndex);
			fireTabEditing(TabEditingEvent.TAB_EDITING_STOPPED, tabIndex,
					oldTitle, newTitle);
		}
	}

	public void cancelTabEditing() {
		int tabIndex = getEditingTabIndex();
		if (tabIndex != -1) {
			((JideTabbedPaneUI) getUI()).cancelTabEditing();
			fireTabEditing(TabEditingEvent.TAB_EDITING_CANCELLED, tabIndex,
					getTitleAt(tabIndex), getTitleAt(tabIndex));
		}
	}

	public int getEditingTabIndex() {
		return ((JideTabbedPaneUI) getUI()).getEditingTabIndex();
	}

	protected PropertyChangeListener _focusChangeListener;

	protected PropertyChangeListener createFocusChangeListener() {
		return new PropertyChangeListener() {
			public void propertyChange(PropertyChangeEvent evt) {
				final boolean hadFocus = JideTabbedPane.this
						.isAncestorOf((Component) evt.getOldValue())
						|| JideTabbedPane.this == evt.getOldValue();
				boolean hasFocus = JideTabbedPane.this == evt.getNewValue()
						|| JideTabbedPane.this.hasFocusComponent();
				if (hasFocus != hadFocus) {
					repaintTabAreaAndContentBorder();
				}
			}
		};
	}

	/**
	 * Repaints the tab area and the content border if any. This is mainly for
	 * the focus border in JideTabbedPane Office2003 and Eclipse3x style.
	 */
	public void repaintTabAreaAndContentBorder() {
		int delay = 200;
		((JideTabbedPaneUI) getUI()).getTabPanel().repaint(delay);

		Insets contentinsets = getContentBorderInsets();
		if (contentinsets == null) {
			LookAndFeelFactory.installJideExtension();
			contentinsets = getContentBorderInsets();
		}

		if (contentinsets != null
				&& (contentinsets.top != 0 || contentinsets.bottom != 0
						|| contentinsets.left != 0 || contentinsets.right != 0)) {
			Insets insets = new Insets(0, 0, 0, 0);
			BasicJideTabbedPaneUI.rotateInsets(contentinsets, insets,
					tabPlacement);
			switch (getTabPlacement()) {
			case TOP:
				insets.top += getTabHeight();
				break;
			case BOTTOM:
				insets.bottom += getTabHeight();
				break;
			case LEFT:
				insets.left += getTabHeight();
				break;
			case RIGHT:
				insets.right += getTabHeight();
				break;
			}
			if (insets.top != 0) {
				repaintContentBorder(0, 0, getWidth(), insets.top);
			}
			if (insets.left != 0) {
				repaintContentBorder(0, 0, insets.left, getHeight());
			}
			if (insets.right != 0) {
				repaintContentBorder(getWidth() - insets.right, 0,
						insets.right, getHeight());
			}
			if (insets.bottom != 0) {
				repaintContentBorder(0, getHeight() - insets.bottom,
						getWidth(), insets.bottom);
			}
		}
	}

	/**
	 * Calls repaint on the specified rectangular area.
	 *
	 * @param x
	 *            the <i>x</i> coordinate
	 * @param y
	 *            the <i>y</i> coordinate
	 * @param width
	 *            the width
	 * @param height
	 *            the height
	 */
	protected void repaintContentBorder(int x, int y, int width, int height) {
		repaint(x, y, width, height);
	}

	@Override
	public void addNotify() {
		super.addNotify();
		if (_focusChangeListener == null) {
			_focusChangeListener = createFocusChangeListener();
			KeyboardFocusManager.getCurrentKeyboardFocusManager()
					.addPropertyChangeListener("focusOwner",
							_focusChangeListener);
		}

	}

	@Override
	public void removeNotify() {
		super.removeNotify();
		if (_focusChangeListener != null) {
			KeyboardFocusManager.getCurrentKeyboardFocusManager()
					.removePropertyChangeListener("focusOwner",
							_focusChangeListener);
			_focusChangeListener = null;
		}
	}

	/**
	 * Gets the tab list cell renderer. This renderer is used to render the list
	 * in the popup when tab list button is pressed.
	 *
	 * @return the tab list cell renderer.
	 * @see #setTabListCellRenderer(javax.swing.ListCellRenderer)
	 */
	public ListCellRenderer getTabListCellRenderer() {
		if (_tabListCellRenderer != null) {
			return _tabListCellRenderer;
		} else {
			return new TabListCellRenderer();
		}
	}

	/**
	 * The default tab list cell renderer used to renderer the list in the popup
	 * when tab list button is pressed.
	 */
	public static class TabListCellRenderer extends DefaultListCellRenderer {
		@Override
		public Component getListCellRendererComponent(JList list, Object value,
				int index, boolean isSelected, boolean cellHasFocus) {
			if (value instanceof JideTabbedPane) {
				JideTabbedPane tabbedPane = (JideTabbedPane) value;
				String title = tabbedPane.getTitleAt(index);
				String tooltip = tabbedPane.getToolTipTextAt(index);
				Icon icon = tabbedPane.getIconForTab(index);
				JLabel label = (JLabel) super.getListCellRendererComponent(
						list, title, index, isSelected, cellHasFocus);
				label.setToolTipText(tooltip);
				Font fnt;
				if (tabbedPane.getSelectedIndex() == index
						&& tabbedPane.getSelectedTabFont() != null) {
					fnt = tabbedPane.getSelectedTabFont();
				} else {
					fnt = tabbedPane.getFont();
				}

				if (tabbedPane.getSelectedIndex() == index
						&& tabbedPane.isBoldActiveTab()
						&& fnt.getStyle() != Font.BOLD) {
					fnt = fnt.deriveFont(Font.BOLD);
				}
				label.setFont(fnt);
				label.setIcon(icon);
				label.setEnabled(tabbedPane.isEnabledAt(index));
				return label;
			} else {
				return super.getListCellRendererComponent(list, value, index,
						isSelected, cellHasFocus);
			}
		}
	}

	/**
	 * Sets the tab list cell renderer. This renderer is used to render the list
	 * in the popup when tab list button is pressed. In this list cell renderer,
	 * the value will always be the JideTabbedPane. The index will tell you
	 * which tab it is. See below for the default cell renderer we used.
	 * <code><pre>
	 * public static class TabListCellRenderer extends DefaultListCellRenderer {
	 *     public Component getListCellRendererComponent(JList list, Object value, int index,
	 * boolean isSelected, boolean cellHasFocus) {
	 *         if (value instanceof JideTabbedPane) { // will always be true
	 *             JideTabbedPane tabbedPane = (JideTabbedPane) value;
	 *             String title = tabbedPane.getTitleAt(index);
	 *             Icon icon = tabbedPane.getIconAt(index);
	 *             JLabel label = (JLabel) super.getListCellRendererComponent(list, title, index,
	 * isSelected, cellHasFocus);
	 *             label.setIcon(icon);
	 *             return label;
	 *         }
	 *         else {
	 *             return super.getListCellRendererComponent(list, value, index, isSelected,
	 * cellHasFocus);
	 *         }
	 *     }
	 * }
	 * </code></pre> You can create your own cell renderer either extending
	 * {@link TabListCellRenderer} or starting from scratch.
	 *
	 * @param tabListCellRenderer
	 *            the cell renderer
	 */
	public void setTabListCellRenderer(ListCellRenderer tabListCellRenderer) {
		_tabListCellRenderer = tabListCellRenderer;
	}

	/**
	 * Checks if the JideTabbedPane has the focus component. If true, in some
	 * styles such as Office2003 style, we will paint a background on the insets
	 * to indicate the tabbed pane has focus.
	 *
	 * @return true if the JideTabbedPane has the focus component. Otherwise
	 *         false.
	 */
	public boolean hasFocusComponent() {
		return JideSwingUtilities.isAncestorOfFocusOwner(this);
	}

	public Insets getContentBorderInsets() {
		return _contentBorderInsets;
	}

	/**
	 * Sets the content border insets. It's the inserts around the
	 * JideTabbedPane's content. The direction of the insets is when the tabs
	 * are on top. We will rotate it automatically when the tabs are on other
	 * directions.
	 *
	 * @param contentBorderInsets
	 *            the content border insets
	 */
	public void setContentBorderInsets(Insets contentBorderInsets) {
		Insets old = _contentBorderInsets;
		_contentBorderInsets = contentBorderInsets;
		firePropertyChange(PROPERTY_CONTENT_BORDER_INSETS, old,
				_contentBorderInsets);
	}

	public Insets getTabAreaInsets() {
		return _tabAreaInsets;
	}

	/**
	 * Sets the tab area insets. It's the inserts around the tabs. The direction
	 * of the insets is when the tabs are on top. We will rotate it
	 * automatically when the tabs are on other directions.
	 *
	 * @param tabAreaInsets
	 *            the content border insets
	 */
	public void setTabAreaInsets(Insets tabAreaInsets) {
		Insets old = _tabAreaInsets;
		_tabAreaInsets = tabAreaInsets;
		firePropertyChange(PROPERTY_TAB_AREA_INSETS, old, _tabAreaInsets);
	}

	public Insets getTabInsets() {
		return _tabInsets;
	}

	/**
	 * Sets the tab insets. It's the inserts around the JideTabbedPane's tab.
	 * The direction of the insets is when the tabs are on top. We will rotate
	 * it automatically when the tabs are on other directions.
	 *
	 * @param tabInsets
	 *            the content border insets
	 */
	public void setTabInsets(Insets tabInsets) {
		Insets old = _tabInsets;
		_tabInsets = tabInsets;
		firePropertyChange(PROPERTY_TAB_INSETS, old, _tabInsets);
	}

	/**
	 * Checks the dragOverDisabled property. By default it is false.
	 *
	 * @return true or false.
	 * @see #setDragOverDisabled(boolean)
	 */
	public boolean isDragOverDisabled() {
		return _dragOverDisabled;
	}

	/**
	 * Sets the dragOverDisabled property. Default is false. It means when you
	 * drag something over an unselected tab, the tab will be selected
	 * automatically. You may want to set it to true if you want to add your own
	 * drop listener to the tabs.
	 *
	 * @param dragOverDisabled
	 *            the flag indicating if drag over is disabled
	 */
	public void setDragOverDisabled(boolean dragOverDisabled) {
		boolean old = _dragOverDisabled;
		if (old != dragOverDisabled) {
			_dragOverDisabled = dragOverDisabled;
			firePropertyChange(PROPERTY_DRAG_OVER_DISABLED, old,
					dragOverDisabled);
		}
	}

	/**
	 * Scroll the selected tab visible in case the tab is outside of the
	 * viewport.
	 *
	 * @param scrollLeft
	 *            true to scroll the first tab visible first then scroll left to
	 *            make the selected tab visible. This will get a more consistent
	 *            result. If false, it will simple scroll the selected tab
	 *            visible. Sometimes the tab will appear as the first visible
	 *            tab or the last visible tab depending on the previous viewport
	 *            position.
	 */
	public void scrollSelectedTabToVisible(boolean scrollLeft) {
		((JideTabbedPaneUI) getUI()).ensureActiveTabIsVisible(scrollLeft);
	}

	/**
	 * Adds a <code>TabEditingListener</code> to this tabbedpane.
	 *
	 * @param l
	 *            the <code>TabEditingListener</code> to add
	 * @see #fireTabEditing
	 * @see #removeTabEditingListener(TabEditingListener)
	 * @see #getTabEditingListeners()
	 */
	public void addTabEditingListener(TabEditingListener l) {
		listenerList.add(TabEditingListener.class, l);
	}

	/**
	 * Removes a <code>TabEditingListener</code> from this tabbedpane.
	 *
	 * @param l
	 *            the <code>TabEditingListener</code> to remove
	 * @see #fireTabEditing
	 * @see #addTabEditingListener
	 */
	public void removeTabEditingListener(TabEditingListener l) {
		listenerList.remove(TabEditingListener.class, l);
	}

	/**
	 * Returns an array of all the <code>TabEditingListener</code>s added to
	 * this <code>JTabbedPane</code> with <code>addTabEditingListener</code>.
	 *
	 * @return all of the <code>TabEditingListener</code>s added or an empty
	 *         array if no listeners have been added
	 */
	public TabEditingListener[] getTabEditingListeners() {
		return listenerList.getListeners(TabEditingListener.class);
	}

	protected void fireTabEditing(int id, int index, String oldTitle,
			String newTitle) {
		if (LOGGER_EVENT.isLoggable(Level.FINE)) {
			switch (id) {
			case TabEditingEvent.TAB_EDITING_STARTED:
				LOGGER_EVENT.fine("TabEditing Started at tab \"" + index
						+ "\"; the current title is " + oldTitle);
				break;
			case TabEditingEvent.TAB_EDITING_STOPPED:
				LOGGER_EVENT.fine("TabEditing Stopped at tab \"" + index
						+ "\"; the old title is " + oldTitle
						+ "; the new title is " + newTitle);
				break;
			case TabEditingEvent.TAB_EDITING_CANCELLED:
				LOGGER_EVENT.fine("TabEditing Cancelled at tab \"" + index
						+ "\"; the current title remains " + oldTitle);
				break;
			}
		}
		Object[] listeners = listenerList.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == TabEditingListener.class) {
				TabEditingEvent tabEditingEvent = new TabEditingEvent(this, id,
						index, oldTitle, newTitle);
				if (id == TabEditingEvent.TAB_EDITING_STARTED) {
					((TabEditingListener) listeners[i + 1])
							.editingStarted(tabEditingEvent);
				} else if (id == TabEditingEvent.TAB_EDITING_CANCELLED) {
					((TabEditingListener) listeners[i + 1])
							.editingCanceled(tabEditingEvent);
				} else if (id == TabEditingEvent.TAB_EDITING_STOPPED) {
					((TabEditingListener) listeners[i + 1])
							.editingStopped(tabEditingEvent);
				}
			}
		}
	}

	/**
	 * Gets the icon for the tab after looking at the UIDefault
	 * "JideTabbedPane.showIconOnTab" and {@link #isShowIconsOnTab()}. Note that
	 * getIconAt method will always return the tab even though the icon is not
	 * displayed because the two flags above.
	 *
	 * @param tabIndex
	 *            the tab index.
	 * @return the icon for the tab at the specified index.
	 */
	public Icon getIconForTab(int tabIndex) {
		boolean _showIconOnTab = UIDefaultsLookup
				.getBoolean("JideTabbedPane.showIconOnTab");
		if (isUseDefaultShowIconsOnTab()) {
			if (_showIconOnTab) {
				return (!isEnabled() || !isEnabledAt(tabIndex)) ? getDisabledIconAt(tabIndex)
						: getIconAt(tabIndex);
			} else {
				return null;
			}
		} else if (isShowIconsOnTab()) {
			return (!isEnabled() || !isEnabledAt(tabIndex)) ? getDisabledIconAt(tabIndex)
					: getIconAt(tabIndex);
		} else {
			return null;
		}
	}

	/**
	 * Checks if the selected tab will be changed on mouse wheel event.
	 *
	 * @return true or false.
	 */
	public boolean isScrollSelectedTabOnWheel() {
		return _scrollSelectedTabOnWheel;
	}

	/**
	 * If true, the selected tab will be changed on mouse wheel. It is false by
	 * default.
	 *
	 * @param scrollSelectedTabOnWheel
	 *            the flag
	 */
	public void setScrollSelectedTabOnWheel(boolean scrollSelectedTabOnWheel) {
		boolean oldValue = isScrollSelectedTabOnWheel();
		if (oldValue != scrollSelectedTabOnWheel) {
			_scrollSelectedTabOnWheel = scrollSelectedTabOnWheel;
			firePropertyChange(SCROLL_TAB_ON_WHEEL_PROPERTY, oldValue,
					_scrollSelectedTabOnWheel);
		}
	}

	/**
	 * Get the flag if clicking middle mouse button can close the tab. It is
	 * false by default.
	 *
	 * @return the flag.
	 */
	public boolean isCloseTabOnMouseMiddleButton() {
		return _closeTabOnMouseMiddleButton;
	}

	/**
	 * Set the flag if clicking middle mouse button can close the tab. It is
	 * false by default.
	 *
	 * @param closeTabOnMouseMiddleButton
	 *            the flag
	 */
	public void setCloseTabOnMouseMiddleButton(
			boolean closeTabOnMouseMiddleButton) {
		this._closeTabOnMouseMiddleButton = closeTabOnMouseMiddleButton;
	}

	/**
	 * Returns the alignment of the tabs for this tabbed pane.
	 *
	 * @return the alignment of the tabs for this tabbed pane.
	 * @see #setTabAlignment(int)
	 */
	public int getTabAlignment() {
		return _tabAlignment;
	}

	/**
	 * Sets the tab alignment for the tabs of a tabbed pane. Currently it only
	 * supports top and bottom tab placement. Possible values are:
	 * <ul>
	 * <li><code>JideTabbedPane.LEADING</code>
	 * <li><code>JideTabbedPane.CENTER</code>
	 * </ul>
	 * The default value, if not set, is <code>JideTabbedPane.LEADING</code>.
	 *
	 * @param tabAlignment
	 *            the alignment for the tabs relative to the content
	 * @throws IllegalArgumentException
	 *             if tab alignment value isn't one of the above valid values
	 */
	public void setTabAlignment(int tabAlignment) {
		if (tabAlignment != LEADING && tabAlignment != CENTER) {
			throw new IllegalArgumentException(
					"illegal tab alignment: must be LEADING or CENTER");
		}
		if (_tabAlignment != tabAlignment) {
			int oldValue = _tabAlignment;
			_tabAlignment = tabAlignment;
			firePropertyChange("tabAlignment", oldValue, tabAlignment);
			revalidate();
			repaint();
		}
	}

	/**
	 * Gets the resource string used in JideTabbedPane. Subclass can override it
	 * to provide their own strings.
	 *
	 * @param key
	 *            the resource key
	 * @return the localized string.
	 */
	public String getResourceString(String key) {
		return com.jidesoft.plaf.basic.Resource.getResourceBundle(getLocale())
				.getString(key);
	}

	/**
	 * Creates tab list popup.
	 *
	 * @return the tab list popup instance.
	 * @since 3.2.2
	 */
	protected JidePopup createTabListPopup() {
		return new JidePopup();
	}

	/**
	 * Checks if the tab list popup is visible.
	 *
	 * @return true if the tab list popup is visible. Otherwise false.
	 * @since 3.2.2
	 */
	public boolean isTabListPopupVisible() {
		return _tabListPopup != null && _tabListPopup.isPopupVisible();
	}

	/**
	 * Hides the tab list popup if it's visible.
	 *
	 * @since 3.2.2
	 */
	public void hideTabListPopup() {
		if (_tabListPopup != null) {
			if (_tabListPopup.isPopupVisible()) {
				_tabListPopup.hidePopupImmediately();
			}
			_tabListPopup = null;
		}
	}

	/**
	 * Shows the tab list popup by clicking on the list button.
	 *
	 * @param listButton
	 *            the list button being clicked.
	 * @since 3.2.2
	 */
	public void showTabListPopup(JButton listButton) {
		JPanel panel = new JPanel(new BorderLayout());
		panel.setBackground(UIDefaultsLookup
				.getColor("JideTabbedPane.tabListBackground"));
		panel.setOpaque(true);
		panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));

		JList list = createTabList(panel.getInsets());
		JScrollPane scroller = new JScrollPane(list);
		scroller.setBorder(BorderFactory.createEmptyBorder());
		scroller.getViewport().setOpaque(false);
		scroller.setOpaque(false);
		panel.add(scroller);

		hideTabListPopup();
		_tabListPopup = createTabListPopup();
		_tabListPopup.setComponentOrientation(getComponentOrientation());
		_tabListPopup.setPopupBorder(BorderFactory
				.createLineBorder(UIDefaultsLookup
						.getColor("JideTabbedPane.darkShadow")));
		_tabListPopup.add(panel);
		_tabListPopup.addExcludedComponent(listButton);
		_tabListPopup.setDefaultFocusComponent(list);

		_tabListPopup.setOwner(this);
		_tabListPopup.removeExcludedComponent(this);

		Point point = calculateTabListPopupPosition(listButton);
		_tabListPopup.showPopup(point.x, point.y);
	}

	/**
	 * Calculates the position where the tab list popup is to be displayed based
	 * on the list button being clicked.
	 *
	 * @param listButton
	 *            the list button being clicked.
	 * @return the point.
	 * @since 3.2.2
	 */
	protected Point calculateTabListPopupPosition(JButton listButton) {
		Dimension size = _tabListPopup.getPreferredSize();
		Rectangle bounds = listButton.getBounds();
		Point p = listButton.getLocationOnScreen();
		bounds.x = p.x;
		bounds.y = p.y;
		int x;
		int y;
		switch (getTabPlacement()) {
		case TOP:
		default:
			if (getComponentOrientation().isLeftToRight()) {
				x = bounds.x + bounds.width - size.width;
			} else {
				x = bounds.x;
			}
			y = bounds.y + bounds.height + 2;
			break;
		case BOTTOM:
			if (getComponentOrientation().isLeftToRight()) {
				x = bounds.x + bounds.width - size.width;
			} else {
				x = bounds.x;
			}
			y = bounds.y - size.height - 2;
			break;
		case LEFT:
			x = bounds.x + bounds.width + 2;
			y = bounds.y + bounds.height - size.height;
			break;
		case RIGHT:
			x = bounds.x - size.width - 2;
			y = bounds.y + bounds.height - size.height;
			break;
		}

		Rectangle screenBounds = PortingUtils.getScreenBounds(this, true);
		int right = x + size.width + 3;
		int bottom = y + size.height + 3;

		if (right > screenBounds.x + screenBounds.width) {
			x -= right - screenBounds.x - screenBounds.width; // move left so
																// that the
																// whole popup
																// can fit in
		}

		if (x < screenBounds.x) {
			x = screenBounds.x; // move right so that the whole popup can fit in
		}

		if (bottom > screenBounds.height) {
			y -= bottom - screenBounds.height;
		}

		if (y < screenBounds.y) {
			y = screenBounds.y;
		}
		return new Point(x, y);
	}

	/**
	 * Creates the tab list.
	 *
	 * @param insets
	 *            the insets of its parent container which helps determine the
	 *            visible row count of the list.
	 * @return the created list instance.
	 * @since 3.2.2
	 */
	protected JList createTabList(Insets insets) {
		final JList list = new JList() {
			// override this method to disallow deselect by ctrl-click
			@Override
			public void removeSelectionInterval(int index0, int index1) {
				super.removeSelectionInterval(index0, index1);
				if (getSelectedIndex() == -1) {
					setSelectedIndex(index0);
				}
			}

			@Override
			public Dimension getPreferredScrollableViewportSize() {
				Dimension preferredScrollableViewportSize = super
						.getPreferredScrollableViewportSize();
				if (preferredScrollableViewportSize.width < 150) {
					preferredScrollableViewportSize.width = 150;
				}
				int screenWidth = PortingUtils.getScreenSize(this).width;
				if (preferredScrollableViewportSize.width >= screenWidth) {
					preferredScrollableViewportSize.width = screenWidth;
				}
				return preferredScrollableViewportSize;
			}

			@Override
			public Dimension getPreferredSize() {
				Dimension preferredSize = super.getPreferredSize();
				int screenWidth = PortingUtils.getScreenSize(this).width;
				if (preferredSize.width >= screenWidth) {
					preferredSize.width = screenWidth;
				}
				return preferredSize;
			}
		};
		DefaultListModel listModel = new DefaultListModel();

		// drop down menu items
		int selectedIndex = getSelectedIndex();
		int totalCount = getTabCount();
		for (int i = 0; i < totalCount; i++) {
			listModel.addElement(this);
		}
		list.setCellRenderer(getTabListCellRenderer());
		list.setModel(listModel);
		list.setSelectedIndex(selectedIndex);
		list.addKeyListener(new KeyListener() {
			public void keyTyped(KeyEvent e) {
			}

			public void keyPressed(KeyEvent e) {
				if (e.getKeyCode() == KeyEvent.VK_ENTER) {
					componentSelected(list);
				}
			}

			public void keyReleased(KeyEvent e) {
			}
		});
		list.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseReleased(MouseEvent e) {
				componentSelected(list);
			}
		});

		list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		int max = (PortingUtils.getLocalScreenSize(this).height - insets.top - insets.bottom)
				/ list.getCellBounds(0, 0).height;
		if (listModel.getSize() > max) {
			list.setVisibleRowCount(max);
		} else {
			list.setVisibleRowCount(listModel.getSize());
		}
		new Sticky(list);
		list.setBackground(UIDefaultsLookup
				.getColor("JideTabbedPane.tabListBackground"));
		return list;
	}

	private void componentSelected(JList list) {
		int tabIndex = list.getSelectedIndex();
		if (tabIndex != -1 && isEnabledAt(tabIndex)) {
			if (tabIndex == getSelectedIndex()
					&& JideSwingUtilities.isAncestorOfFocusOwner(this)) {
				if (isAutoFocusOnTabHideClose() && isRequestFocusEnabled()) {
					Runnable runnable = new Runnable() {
						public void run() {
							requestFocus();
						}
					};
					SwingUtilities.invokeLater(runnable);
				}
			} else {
				setSelectedIndex(tabIndex);
				final Component comp = getComponentAt(tabIndex);
				if (isAutoFocusOnTabHideClose() && !comp.isVisible()
						&& SystemInfo.isJdk15Above()
						&& !SystemInfo.isJdk6Above()) {
					comp.addComponentListener(new ComponentAdapter() {
						@Override
						public void componentShown(ComponentEvent e) {
							// remove the listener
							comp.removeComponentListener(this);

							final Component lastFocused = getLastFocusedComponent(comp);
							Runnable runnable = new Runnable() {
								public void run() {
									if (lastFocused != null) {
										lastFocused.requestFocus();
									} else if (isRequestFocusEnabled()) {
										requestFocus();
									}
								}
							};
							SwingUtilities.invokeLater(runnable);
						}
					});
				} else {
					final Component lastFocused = getLastFocusedComponent(comp);
					if (lastFocused != null) {
						Runnable runnable = new Runnable() {
							public void run() {
								lastFocused.requestFocus();
							}
						};
						SwingUtilities.invokeLater(runnable);
					} else {
						Container container;
						if (comp instanceof Container) {
							container = (Container) comp;
						} else {
							container = comp.getFocusCycleRootAncestor();
						}
						FocusTraversalPolicy traversalPolicy = container
								.getFocusTraversalPolicy();
						Component focusComponent;
						if (traversalPolicy != null) {
							focusComponent = traversalPolicy
									.getDefaultComponent(container);
							if (focusComponent == null) {
								focusComponent = traversalPolicy
										.getFirstComponent(container);
							}
						} else if (comp instanceof Container) {
							// not sure if it is correct
							focusComponent = findFocusableComponent((Container) comp);
						} else {
							focusComponent = comp;
						}
						if (focusComponent != null) {
							final Component theComponent = focusComponent;
							Runnable runnable = new Runnable() {
								public void run() {
									theComponent.requestFocus();
								}
							};
							SwingUtilities.invokeLater(runnable);
						}
					}
				}
			}
			if (getUI() instanceof BasicJideTabbedPaneUI) {
				((BasicJideTabbedPaneUI) getUI())
						.ensureActiveTabIsVisible(false);
			}
			hideTabListPopup();
		}
	}

	private Component findFocusableComponent(Container parent) {
		FocusTraversalPolicy traversalPolicy = parent.getFocusTraversalPolicy();
		Component focusComponent = null;
		if (traversalPolicy != null) {
			focusComponent = traversalPolicy.getDefaultComponent(parent);
			if (focusComponent == null) {
				focusComponent = traversalPolicy.getFirstComponent(parent);
			}
		}
		if (focusComponent != null) {
			return focusComponent;
		}
		int i = 0;
		while (i < parent.getComponentCount()) {
			Component comp = parent.getComponent(i);
			if (comp instanceof Container) {
				focusComponent = findFocusableComponent((Container) comp);
				if (focusComponent != null) {
					return focusComponent;
				}
			} else if (comp.isFocusable()) {
				return comp;
			}
			i++;
		}
		if (parent.isFocusable()) {
			return parent;
		}
		return null;
	}

	/**
	 * Creates no focus buttons for JideTabbedPane.
	 *
	 * @param type
	 *            the button type, it could be {@link #BUTTON_LIST},
	 *            {@link #BUTTON_CLOSE}, {@link #BUTTON_EAST},
	 *            {@link #BUTTON_WEST}, {@link #BUTTON_NORTH} or
	 *            {@link #BUTTON_SOUTH}
	 * @return the button instance.
	 */
	public NoFocusButton createNoFocusButton(int type) {
		return new NoFocusButton(type);
	}

	public class NoFocusButton extends JButton implements MouseMotionListener,
			MouseListener, UIResource {
		private int _type;
		private int _index = -1;
		private boolean _mouseOver = false;
		private boolean _mousePressed = false;

		/**
		 * Resets the UI property to a value from the current look and feel.
		 *
		 * @see JComponent#updateUI
		 */
		@Override
		public void updateUI() {
			super.updateUI();
			setMargin(new Insets(0, 0, 0, 0));
			setBorder(BorderFactory.createEmptyBorder());
			setFocusPainted(false);
			setFocusable(false);
			setRequestFocusEnabled(false);
			String name = getName();
			if (name != null)
				setToolTipText(getResourceString(name));
		}

		public NoFocusButton() {
			this(BUTTON_CLOSE);
		}

		public NoFocusButton(int type) {
			addMouseMotionListener(this);
			addMouseListener(this);
			setFocusPainted(false);
			setFocusable(false);
			setType(type);
		}

		@Override
		public Dimension getPreferredSize() {
			return new Dimension(16, 16);
		}

		@Override
		public Dimension getMinimumSize() {
			return new Dimension(5, 5);
		}

		public int getIndex() {
			return _index;
		}

		public void setIndex(int index) {
			_index = index;
		}

		@Override
		public Dimension getMaximumSize() {
			return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
		}

		@Override
		protected void paintComponent(Graphics g) {
			if (getIcon() != null) {
				super.paintComponent(g);
				return;
			}
			if (!isEnabled()) {
				setMouseOver(false);
				setMousePressed(false);
			}
			if (isMouseOver() && isMousePressed()) {
				g.setColor(getPressedShadowColor());
				g.drawLine(0, 0, getWidth() - 1, 0);
				g.drawLine(0, getHeight() - 2, 0, 1);
				g.setColor(getShadowColor());
				g.drawLine(getWidth() - 1, 1, getWidth() - 1, getHeight() - 2);
				g.drawLine(getWidth() - 1, getHeight() - 1, 0, getHeight() - 1);
			} else if (isMouseOver()) {
				g.setColor(getShadowColor());
				g.drawLine(0, 0, getWidth() - 1, 0);
				g.drawLine(0, getHeight() - 2, 0, 1);
				g.setColor(getPressedShadowColor());
				g.drawLine(getWidth() - 1, 1, getWidth() - 1, getHeight() - 2);
				g.drawLine(getWidth() - 1, getHeight() - 1, 0, getHeight() - 1);
			}
			g.setColor(getForegroundColor());
			int centerX = getWidth() >> 1;
			int centerY = getHeight() >> 1;
			int type = getType();
			if ((getTabPlacement() == TOP || getTabPlacement() == BOTTOM)
					&& !JideTabbedPane.this.getComponentOrientation()
							.isLeftToRight()) {
				if (type == BUTTON_EAST) {
					type = BUTTON_WEST;
				} else if (type == BUTTON_WEST) {
					type = BUTTON_EAST;
				}
			}
			switch (type) {
			case BUTTON_CLOSE:
				if (isShowCloseButtonOnMouseOver() && !isMouseOver()) {
					Object property = JideTabbedPane.this
							.getClientProperty("JideTabbedPane.mouseOverTabIndex");
					if (property instanceof Integer && getIndex() >= 0
							&& (Integer) property != getIndex()) {
						return;
					}
				}
				if (isEnabled()) {
					g.drawLine(centerX - 3, centerY - 3, centerX + 3,
							centerY + 3);
					g.drawLine(centerX - 4, centerY - 3, centerX + 2,
							centerY + 3);
					g.drawLine(centerX + 3, centerY - 3, centerX - 3,
							centerY + 3);
					g.drawLine(centerX + 2, centerY - 3, centerX - 4,
							centerY + 3);
				} else {
					g.drawLine(centerX - 3, centerY - 3, centerX + 3,
							centerY + 3);
					g.drawLine(centerX + 3, centerY - 3, centerX - 3,
							centerY + 3);
				}
				break;
			case BUTTON_EAST:
			//
			// |
			// ||
			// |||
			// ||||
			// ||||*
			// ||||
			// |||
			// ||
			// |
			//
			{
				if (getTabPlacement() == TOP || getTabPlacement() == BOTTOM) {
					int x = centerX + 2, y = centerY; // start point. mark as *
														// above
					if (isEnabled()) {
						g.drawLine(x - 4, y - 4, x - 4, y + 4);
						g.drawLine(x - 3, y - 3, x - 3, y + 3);
						g.drawLine(x - 2, y - 2, x - 2, y + 2);
						g.drawLine(x - 1, y - 1, x - 1, y + 1);
						g.drawLine(x, y, x, y);
					} else {
						g.drawLine(x - 4, y - 4, x, y);
						g.drawLine(x - 4, y - 4, x - 4, y + 4);
						g.drawLine(x - 4, y + 4, x, y);
					}

				} else {
					int x = centerX + 3, y = centerY - 2; // start point. mark
															// as * above
					if (isEnabled()) {
						g.drawLine(x - 8, y, x, y);
						g.drawLine(x - 7, y + 1, x - 1, y + 1);
						g.drawLine(x - 6, y + 2, x - 2, y + 2);
						g.drawLine(x - 5, y + 3, x - 3, y + 3);
						g.drawLine(x - 4, y + 4, x - 4, y + 4);
					} else {
						g.drawLine(x - 8, y, x, y);
						g.drawLine(x - 8, y, x - 4, y + 4);
						g.drawLine(x - 4, y + 4, x, y);
					}
				}
			}
				break;
			case BUTTON_WEST: {
				//
				// |
				// ||
				// |||
				// ||||
				// *||||
				// ||||
				// |||
				// ||
				// |
				//
				{
					if (getTabPlacement() == TOP || getTabPlacement() == BOTTOM) {
						int x = centerX - 3, y = centerY; // start point. mark
															// as * above
						if (isEnabled()) {
							g.drawLine(x, y, x, y);
							g.drawLine(x + 1, y - 1, x + 1, y + 1);
							g.drawLine(x + 2, y - 2, x + 2, y + 2);
							g.drawLine(x + 3, y - 3, x + 3, y + 3);
							g.drawLine(x + 4, y - 4, x + 4, y + 4);
						} else {
							g.drawLine(x, y, x + 4, y - 4);
							g.drawLine(x, y, x + 4, y + 4);
							g.drawLine(x + 4, y - 4, x + 4, y + 4);
						}
					} else {

						int x = centerX - 5, y = centerY + 3; // start point.
																// mark as *
																// above
						if (isEnabled()) {
							g.drawLine(x, y, x + 8, y);
							g.drawLine(x + 1, y - 1, x + 7, y - 1);
							g.drawLine(x + 2, y - 2, x + 6, y - 2);
							g.drawLine(x + 3, y - 3, x + 5, y - 3);
							g.drawLine(x + 4, y - 4, x + 4, y - 4);
						} else {
							g.drawLine(x, y, x + 8, y);
							g.drawLine(x, y, x + 4, y - 4);
							g.drawLine(x + 8, y, x + 4, y - 4);
						}
					}
				}
				break;
			}
			case BUTTON_LIST: {
				int x = centerX + 2, y = centerY; // start point. mark as
				// * above
				g.drawLine(x - 6, y - 4, x - 6, y + 4);
				g.drawLine(x + 1, y - 4, x + 1, y + 4);
				g.drawLine(x - 6, y - 4, x + 1, y - 4);
				g.drawLine(x - 4, y - 2, x - 1, y - 2);
				g.drawLine(x - 4, y, x - 1, y);
				g.drawLine(x - 4, y + 2, x - 1, y + 2);
				g.drawLine(x - 6, y + 4, x + 1, y + 4);
				break;
			}
			}
		}

		protected Color getForegroundColor() {
			return UIDefaultsLookup.getColor("JideTabbedPane.foreground");
		}

		protected Color getShadowColor() {
			return UIDefaultsLookup.getColor("control");
		}

		protected Color getPressedShadowColor() {
			return UIDefaultsLookup.getColor("controlDkShadow");
		}

		@Override
		public boolean isFocusable() {
			return false;
		}

		@Override
		public void requestFocus() {
		}

		@Override
		public boolean isOpaque() {
			return false;
		}

		public void mouseDragged(MouseEvent e) {
		}

		public void mouseMoved(MouseEvent e) {
			if (!isEnabled())
				return;
			setMouseOver(true);
			repaint();
		}

		public void mouseClicked(MouseEvent e) {
			if (!isEnabled())
				return;
			setMouseOver(true);
			setMousePressed(false);
		}

		public void mousePressed(MouseEvent e) {
			if (!isEnabled())
				return;
			setMousePressed(true);
			repaint();
		}

		public void mouseReleased(MouseEvent e) {
			if (!isEnabled())
				return;
			setMousePressed(false);
			setMouseOver(false);
		}

		public void mouseEntered(MouseEvent e) {
			if (!isEnabled())
				return;
			setMouseOver(true);
			repaint();
		}

		public void mouseExited(MouseEvent e) {
			if (!isEnabled())
				return;
			setMouseOver(false);
			setMousePressed(false);
			repaint();
			JideTabbedPane.this.repaint();
		}

		public int getType() {
			return _type;
		}

		public void setType(int type) {
			_type = type;
		}

		public boolean isMouseOver() {
			return _mouseOver;
		}

		public void setMouseOver(boolean mouseOver) {
			_mouseOver = mouseOver;
		}

		public boolean isMousePressed() {
			return _mousePressed;
		}

		public void setMousePressed(boolean mousePressed) {
			_mousePressed = mousePressed;
		}
	}

	@Override
	public void removeAll() {
		super.removeAll();
		updateUI();
	}
}